Skip to content

13 对象

Xmind

object xmind

什么是对象

  1. 对象是什么?
    • 对象是一种数据类型
    • 无序的数据的集合
  2. 对象有什么特点?
    • 无序的数据的集合
    • 可以详细的描述描述某个事物
  • 对象(object):JavaScript 里的一种复合数据类型,用于存储和组织相关的数据和功能。对象是由属性和方法组成的,属性可以是任何数据类型,包括字符串、数字、布尔值、数组、函数等等。

  • 可以理解为是一种无序的数据集合,注意数组是有序的数据集合

  • 用来描述某个事物,例如描述一个人

    • 人有姓名、年龄、性别等信息、还有吃饭睡觉打代码等功能
    • 如果用多个变量保存则比较散,用对象比较统一
  • 比如描述 班主任 信息:

    • 静态特征 (姓名,年龄,身高,性别,爱好) => 可以使用数字,字符串,数组,布尔类型等表示
    • 动态行为 (点名,唱,跳,rap) => 使用函数表示

对象使用

  1. 对象属性有顺序吗?
    • 没有
  2. 属性和值用什么符号隔开?多个属性用什么隔开?
    • 属性和值用 ; 隔开
    • 多个属性用 , 逗号隔开
  3. 对象查语法如何写?
    • 对象名.属性
  4. 对象改语法如何写:
    • 对象名.属性 = 新值
  5. 对象增语法如何写:
    • 对象名.新属性名 = 新值
    • 改和增语法一样,判断标准就是对象有没有这个属性,没有就是新增,有就是改
  6. 对象访问属性有哪两种方式?
    • 点形式 对象.属性
    • [] 形式 对象['属性']
  7. 两种方式有什么区别?
    • 点后面的属性名一定不要加引号
    • [] 里面的属性名一定加引号
    • 后期不同使用场景会用到不同的写法
  8. 对象访问方法是如何实现的?
    • 对象.方法 ()
    • person.sayHi()
  9. 对象方法可以传递参数吗?
    • 可以,跟函数使用方法基本一致

对象有属性和方法组成。

  • 属性:信息或叫特征(名词)。比如 手机尺寸、颜色、重量等…
  • 方法:功能或叫行为(动词)。比如 手机打电话、发短信、玩游戏…

对象声明语法

对象可以通过两种方式创建:字 面量和构造函数。

对象字面量

对象字面量语法是最常用的声明对象的方式,它使用一对花括号 {} 来定义对象,对象中包含若干属性和方法。

  • 属性名和属性值之间使用冒号 : 分隔
  • 属性之间使用逗号 , 分隔
  • 方法名和方法体之间使用冒号 : 分隔
  • 方法之间也使用逗号 , 分隔。
js
// 对象字面量语法
let obj = {key1: value1, key2: value2};

let obj = {
  property1: value1,
  property2: value2,
  method1: function () {
    // 方法体
  },
  // ...
};
js
let person = {
  name: "John",
  age: 30,
  address: {
    street: "123 Main St",
    city: "Anytown",
    state: "CA",
  },
  sayHello: function () {
    console.log("Hello, my name is " + this.name);
  },
};

console.log(person.name); // 输出 "John"
console.log(person.address.city); // 输出 "Anytown"
console.log(person["age"]); // 输出 30

person.sayHello(); // 输出 "Hello, my name is John"
person["sayHello"](); // 输出 "Hello, my name is John"

构造函数

构造函数语法是另一种声明对象的方式,它通过定义一个构造函数来创建对象。

js
function ObjectName(property1, property2, ...) {
  this.property1 = value1;
  this.property2 = value2;
  this.method1 = function() {
    // 方法体
  };
  // ...
}
  • ObjectName 是对象的名称
  • property1property2 等是对象的属性名
  • value1value2 等是属性的初始值
  • 方法的定义方式和对象字面量语法相同
js
function Person(name, age) {
  this.name = name;
  this.age = age;

  // 动态添加方法
  this.sayHello = function () {
    console.log("Hello, my name is " + this.name + ", and I'm " + this.age + " years old.");
  };
}

// 通过 new 关键字调用构造函数,可以创建一个新的对象
let person1 = new Person("Tom", 20);
let person2 = new Person("Jerry", 18);
js
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function () {
  console.log("Hello, my name is " + this.name);
};

// 通过 new 关键字调用构造函数,可以创建多个 Person 对象
let john = new Person("John", 30);
let jane = new Person("Jane", 25);

console.log(john.name); // 输出 "John"
console.log(jane.age); // 输出 25

john.sayHello(); // 输出 "Hello, my name is John"
jane.sayHello(); // 输出 "Hello, my name is Jane"

属性

  • 数据描述性的信息称为属性,如人的姓名、身高、年龄、性别等,一般是名词性的。
  • 属性都是成对出现的,包括属性名和值,它们之间使用英文 : 分隔
  • 多个属性之间使用英文 , 分隔
  • 属性就是依附在对象上的变量(外面是变量,对象内是属性)
  • 属性名可以使用 ""'',一般情况下省略,除非名称遇到特殊符号如空格、中横线等
js
// 通过对象描述一个人的数据信息
// person 是一个对象,它包含了一个属性 name
// 属性都是成对出现的,属性名 和 值,它们之间使用英文 : 分隔
let person = {
  name: "小明", // 描述人的姓名
  age: 18, // 描述人的年龄
  stature: 185, // 描述人的身高
  gender: "男", // 描述人的性别
};
案例练习 - 练习使用对象

请声明一个产品对象,里面包如下信息:

要求:

  • 商品对象名字: goods
  • 商品名称命名为: name
  • 商品编号: num
  • 商品毛重: weight
  • 商品产地: address
js
let goods = {
  name: "小米 小米 10 青春版",
  num: "100012816024",
  weight: "0.55kg",
  address: "中国大陆",
};

操作对象属性

对象本质是无序的数据集合,操作数据无非就是 增 删 改 查 语法:

  • 增:对象添加新的数据
    • 对象名.新属性名 = 新值
  • 删:删除对象中属性
    • delete 对象名.属性名
  • 改:重新赋值
    • 对象.属性 = 值
  • 查:查询对象
    • 对象.属性

查询对象属性

  • 声明对象,并添加了若干属性后,可以使用 . 获得对象中属性对应的值,称之为属性访问。
  • 语法:对象名.属性
  • 简单理解就是获得对象里面的属性值。
js
// 点号语法:
objectName.propertyName;

// 方括号语法:
objectName["propertyName"];
  • propertyName 是属性的名称。
  • 对于点号语法,propertyName 必须是一个合法的标识符,不能包含空格或其他特殊字符。
  • 对于方括号语法,propertyName 可以是任何字符串。
js
let person = {
  firstName: "John",
  lastName: "Doe",
  age: 30,
};

console.log(person.firstName); // 输出 "John"
console.log(person["lastName"]); // 输出 "Doe"
js
let person = {
  name: "John",
  age: 30,
  address: {
    street: "123 Main St",
    city: "Anytown",
    state: "CA",
  },
  sayHello: function () {
    console.log("Hello, my name is " + this.name);
  },
};

console.log(person.name); // 输出 "John"
console.log(person.address.city); // 输出 "Anytown"
console.log(person["age"]); // 输出 30

person.sayHello(); // 输出 "Hello, my name is John"
person["sayHello"](); // 输出 "Hello, my name is John"

修改对象属性

语法:对象名.属性 = 新值

js
objectName.propertyName = newValue;
objectName["propertyName"] = newValue;
js
let person = {name: "John", age: 30};
person.name = "Jane";
console.log(person); // Output: { name: 'Jane', age: 30 }

let person = {name: "John", age: 30};
let propertyName = "name";
person[propertyName] = "Jane";
console.log(person); // Output: { name: 'Jane', age: 30 }

新增对象属性

语法:对象名.新属性 = 新值

js
objectName.newPropertyName = value;
objectName["newPropertyName"] = value;
js
let person = {};
person.name = "John";
person.age = 30;
person.gender = "male";
console.log(person); // Output: { name: 'John', age: 30, gender: 'male' }

// 等价于
let person = {name: "John", age: 30, gender: "male"};
console.log(person); // Output: { name: 'John', age: 30, gender: 'male' }

如果属性名无法作为标识符(identifier),例如包含空格、破折号等特殊字符,那么我们就需要使用方括号语法来添加新的属性值。

js
let user = {};
user["first name"] = "John";
user["last name"] = "Doe";
console.log(user); // 输出{ 'first name': 'John', 'last name': 'Doe' }

删除对象属性

语法:delete 对象名.属性

js
delete objectName.propertyName;
js
let obj = {name: "Tom", age: 18};
delete obj.age; // 删除 age 属性
console.log(obj); // 输出{ name: 'Tom' }

注意

  • 使用 delete 关键字删除对象属性时,会同时删除属性的值和属性本身,而不仅仅是一些空间上的标记。因此,删除操作是不可逆的。如果你在之后尝试访问这个属性,会返回 undefined

    js
    let obj = {name: "Tom", age: 18};
    delete obj.age; // 删除 age 属性
    console.log(obj); // 输出{ name: 'Tom' }
    
    // 如果尝试检索已删除的属性,则会返回 ndefined。
    console.log(obj.age); // 输出 ndefined
  • delete 操作符被设计用于对象属性。它对变量或函数没有影响。也不应该被用于预定义的 JavaScript 对象属性,否则会使应用程序崩溃。

    js
    delete Math.PI; // 报错

    此时,我们应该始终遵循 " 不要删除不属于你的属性 " 的原则,避免出现程序崩溃的情况。

案例练习 - 练习使用对象

请对产品对象,做如下操作:

要求:

  1. 请将商品名称里面的值修改为:小米 10 PLUS
  2. 新增一个属性颜色 color 为 ' 粉色 '
  3. 请依次页面打印输出所有的属性值
js
var goods = {
  name: "小米 小米 10 青春版",
  num: "100012816024",
  weight: "0.55kg",
  address: "中国大陆",
};

goods.name = "小米 10 PLUS";

goods.color = "粉色";

for (let prop in goods) {
  document.write(prop + ": " + goods[prop]);
}

在使用 for…in 循环遍历对象时,可能会出现某些不期望的结果。例如,循环可能会遍历到从原型链继承而来的属性,而不仅仅是对象本身的属性。此外,还有一些内置属性(比如 length),可能也会被遍历到。如果我们希望只遍历对象本身的属性,可以使用 hasOwnProperty() 方法来进行过滤。

js
for (var prop in goods) {
  // 使用了 hasOwnProperty() 方法来判断当前遍历到的属性是否是对象本身的属性,如果是,则进行输出操作。
  if (goods.hasOwnProperty(prop)) {
    console.log(prop + ": " + goods[prop]);
  }
}

方法

  • 数据行为性的信息称为方法,如跑步、唱歌等,一般是动词性的,其本质是函数。
  • 方法是由方法名和函数两部分构成,它们之间使用 : 分隔
  • 多个属性之间使用英文 , 分隔
  • 方法是依附在对象中的函数
  • 方法名可以使用 ""'',一般情况下省略,除非名称遇到特殊符号如空格、中横线等
  • 声明对象,并添加了若干方法后,可以使用 . 调用对象中函数,我称之为方法调用。
  • 方法也可以添加形参和实参
js
let person = {
  name: "John",
  sayHello: function () {
    // 形参
    console.log("Hello, my name is " + this.name);
  },
};

console.log(person.name); // 输出 "John"

// 方法调用时参数为 实参
person.sayHello(); // 输出 "Hello, my name is John"
person["sayHello"](); // 输出 "Hello, my name is John"
案例练习 - 练习使用对象方法

给对象增加唱歌和跳舞的方法,并打印输出。

js
let person = {
  name: "andy",
  sayHi: function () {
    document.write("hi~~~");
  },

  // 给对象添加唱歌方法
  sing: function () {
    document.write(this.name + "正在唱歌!");
  },
};

// 给对象添加跳舞方法
person.dance = function () {
  document.write(this.name + "正在跳舞!");
};

// 调用对象的唱歌和跳舞方法
person.sing();
person.dance();

person.sayHi();

遍历对象

  1. 遍历对象用那个语句?
    • for in
  2. 遍历对象中,for k in obj,获得对象属性是那个,获得值是那个?
    • 获得对象属性是 k
    • 获得对象值是 obj[k]

for 遍历对象的问题:

  • 对象没有像数组一样的 length 属性,所以无法确定长度
  • 对象里面是无序的键值对,没有规律。不像数组里面有规律的下标
js
let obj = {
  uname: "andy",
  age: 18,
  sex: "男",
};
for (let k in obj) {
  console.log(k); //打印属性名
  console.log(obj[k]); //打印属性值
}
  • 一般不用这种方式遍历数组、主要是用来遍历对象
  • for in 语法中的 k 是一个变量,在循环的过程中依次代表对象的属性名
  • 由于 k 是变量,所以必须使用 [ ] 语法解析
  • 一定记住:k 是获得对象的属性名对象名 [k] 是获得 属性值
案例练习 - 遍历数组对象

遍历数组对象 - 打印学生列表表格 (codepen.io)

需求:请把下面数据中的对象打印出来:

js
// 定义一个存储了若干学生信息的数组
let students = [
  {name: "小明", age: 18, gender: "男", hometown: "河北省"},
  {name: "小红", age: 19, gender: "女", hometown: "河南省"},
  {name: "小刚", age: 17, gender: "男", hometown: "山西省"},
  {name: "小丽", age: 18, gender: "女", hometown: "山东省"},
];

据以上数据渲染生成表格。

html
<div id="result-container"></div>
css
table {
  width: 700px;
  margin: 20px auto;
  border-collapse: collapse;
}

caption {
  font-size: 22px;
  margin-bottom: 10px;
}

tr,
td,
th {
  border: 1px solid #ccc;
  padding: 8px;
  text-align: center;
  font-size: 18px;
}

th {
  background-color: #eee;
}
js
const students = [
  {name: "小明", age: 18, gender: "男", hometown: "河北省"},
  {name: "小红", age: 19, gender: "女", hometown: "河南省"},
  {name: "小刚", age: 17, gender: "男", hometown: "山西省"},
  {name: "小丽", age: 18, gender: "女", hometown: "山东省"},
];

const resultContainer = document.getElementById("result-container");
resultContainer.innerHTML = "";

const studentTable = document.createElement("table");
studentTable.classList.add("student-table");
studentTable.innerHTML = `
    <caption>学生列表</caption>
    <thead>
      <tr>
        <th>序号</th>
        <th>姓名</th>
        <th>年龄</th>
        <th>性别</th>
        <th>家乡</th>
      </tr>
    </thead>
  `;

students.forEach((student, index) => {
  const studentRow = document.createElement("tr");
  studentRow.innerHTML = `
      <td>${index + 1}</td>
      <td>${student.name}</td>
      <td>${student.age}</td>
      <td>${student.gender}</td>
      <td>${student.hometown}</td>
    `;
  studentTable.appendChild(studentRow);
});

studentTable.innerHTML += "</table>";
resultContainer.appendChild(studentTable);

内置对象

内置对象是什么

JavaScript 内部提供的对象,包含各种属性和方法给开发者调用。

常见的 JS 内置对象包括:

  1. Number:用于处理数字,提供了许多数字相关的方法和属性,如 toFixed()toPrecision()toString() 等。
  2. String:用于处理字符串,提供了许多字符串相关的方法和属性,如 charAt()concat()indexOf()、slice() 等。
  3. Boolean:用于处理布尔值,只有两个值:truefalse
  4. Array:用于处理数组,提供了许多数组相关的方法和属性,如 push()pop()shift()unshift() 等。
  5. Object:用于处理对象,提供了许多对象相关的方法和属性,如 hasOwnProperty()keys()values() 等。
  6. Function:用于处理函数,可以定义函数、调用函数、传递参数等。
  7. Date:用于处理日期和时间,提供了许多日期和时间相关的方法和属性,如 getDate()getMonth()getFullYear()getHours() 等。
  8. RegExp:用于处理正则表达式,提供了许多正则表达式相关的方法和属性,如 test()exec() 等。
  9. Math:用于处理数学运算,提供了许多数学相关的方法和属性,如 abs()ceil()floor()max()min() 等。
  10. JSON:用于处理 JSON 数据,提供了许多 JSON 相关的方法和属性,如 parse()stringify() 等。

内置对象 Math

  • 介绍:Math 对象是 JavaScript 提供的一个 " 数学 " 对象
  • 作用:提供了一系列做数学运算的方法
  • Math 对象包含的方法有:
  • random:生成 0-1 之间的随机数(包含 0 不包括 1)
  • ceil:向上取整
  • floor:向下取整
  • max:找最大数
  • min:找最小数
  • pow:幂运算
  • abs:绝对值

Math 常用属性

  • Math.E:自然对数的底数,即 e 的值,约等于 2.718。
  • Math.PI:圆周率的值,约等于 3.1416。
  • Math.SQRT2:2 的平方根,约等于 1.414。
  • Math.SQRT1_2:1/2 的平方根,约等于 0.707。
  • Math.LN2:2 的自然对数,约等于 0.693。
  • Math.LN10:10 的自然对数,约等于 2.303。
  • Math.LOG2E:以 2 为底数的 e 的对数,约等于 1.443。
  • Math.LOG10E:以 10 为底数的 e 的对数,约等于 0.434。

Math 常用方法

  • Math.abs(x):返回 x 的绝对值。
  • Math.ceil(x):返回大于或等于 x 的最小整数。
  • Math.floor(x):返回小于或等于 x 的最大整数。
  • Math.round(x):返回最接近 x 的整数。如果 x 与两个相邻整数的距离相等,则返回偶数。
  • Math.max(x1, x2, …, xn):返回一组数中的最大值。
  • Math.min(x1, x2, …, xn):返回一组数中的最小值。
  • Math.pow(x, y):返回 x 的 y 次幂。
  • Math.sqrt(x):返回 x 的平方根。
  • Math.exp(x):返回 e 的 x 次幂。
  • Math.log(x):返回 x 的自然对数。
  • Math.random():返回一个 0 到 1 之间的随机数。

生成任意范围随机数

  • Math.random() 随机数函数,返回一个 0 - 1 之间,并且包括 0 不包括 1 的随机小数 [0, 1)
  • 如何生成 0-10 的随机数呢?Math.floor(Math.random() * (10 + 1))
  • 如何生成 5-10 的随机数?Math.floor(Math.random() * (5 + 1)) + 5
  • 如何生成 N-M 之间的随机数 Math.floor(Math.random() * (M - N + 1)) + N
案例练习 - 随机点名案例

随机点名案例 (codepen.io)

  • 需求:请把 ['赵云', '黄忠', '关羽', '张飞', '马超', '刘备', '曹操'] 随机显示一个名字到页面中
  • 分析:
    • 利用随机函数随机生成一个数字作为索引号
    • 数组[随机数] 生成到页面中
html
<div class="container">
  <button onclick="randomName()">随机点名</button>
  <p id="result"></p>
</div>
js
const names = ["赵云", "黄忠", "关羽", "张飞", "马超", "刘备", "曹操"];

function randomName() {
  const result = document.getElementById("result");
  result.innerText = names[Math.floor(Math.random() * names.length)];
}
案例练习 - 随机点名案例改进

随机点名案例改进 (codepen.io)

  • 需求:请把 ['赵云', '黄忠', '关羽', '张飞', '马超', '刘备', '曹操'] 随机显示一个名字到页面中,但是不允许重复显示
  • 分析:
    • 利用随机函数随机生成一个数字作为索引号
    • 数组[随机数] 生成到页面中
    • 数组中删除刚才抽中的索引号
html
<button onclick="randomName()">随机点名</button>
<p id="result"></p>
js
const names = ["赵云", "黄忠", "关羽", "张飞", "马超", "刘备", "曹操"];
let remainingNames = [...names];

function randomName() {
  // 判断是否还有剩余的名字可供选择
  if (remainingNames.length === 0) {
    // 如果没有,则将 remainingNames 数组重置为初始状态
    remainingNames = [...names];
  }
  const index = Math.floor(Math.random() * remainingNames.length);
  const name = remainingNames[index];
  // 将对应的名字从 remainingNames 数组中删除
  remainingNames.splice(index, 1);
  const result = document.getElementById("result");
  result.innerText = name;
}
案例练习 - 猜数字游戏

猜数字游戏 (codepen.io)

  • 需求:程序随机生成 1~10 之间的一个数字,用户输入一个数字
    • 如果大于该数字,就提示,数字猜大了,继续猜
    • 如果小于该数字,就提示,数字猜小了,继续猜
    • 如果猜对了,就提示猜对了,程序结束
  • 分析:
    • 利用随机数生成一个数字
    • 需要一直猜,所以需要不断的循环
    • 因为条件是结果猜对了,就是判断条件退出,用 while 循环合适
    • 内部判断可以用多分支语句
js
const randomNumber = Math.floor(Math.random() * 10) + 1;
let guessNumber = parseInt(prompt("请猜一个 1~10 之间的数字"));

while (guessNumber !== randomNumber) {
  if (guessNumber > randomNumber) {
    guessNumber = parseInt(prompt("数字猜大了,请重新输入一个数字"));
  } else {
    guessNumber = parseInt(prompt("数字猜小了,请重新输入一个数字"));
  }
}

alert("恭喜你,猜对了!");
案例练习 - 生成随机颜色

生成随机颜色 (codepen.io)

  • 需求:该函数接收一个布尔类型参数,表示颜色的格式是十六进制还是 rgb 格式。

    • 如果参数传递的是 true 或者无参数,则输出 一个随机十六进制的颜色
    • 如果参数传递的是 false,则输出 一个随机 rgb 的颜色
  • 格式:

    js
    function getRandomColor(flag) {}
    console.log(getRandomColor(true)); //#ffffff
    console.log(getRandomColor(false)); //rgb(255,255,255)
  • 分析:

    • 提示:16 进制颜色格式为:#ffffff 其中 f 可以是任意 0-f 之间的字符,需要用到数组,
      let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
    • 提示:rgb 颜色格式为:rgb(255,255,255) 其中 255 可以是任意 0-255 之间的数字
  • 步骤:

    • 如果参数为 true 或者无参数,则处理 16 进制颜色,核心思想是循环 6 次,生成随机的 6 个数字(取值范围 0~15),根据这个数字去找数组的值,然后和 # 拼接起来,并且返回值。
    • 如果参数为 false,随机生成一个 0~255 的数给三个变量,分别作为 r g b 三个颜色,之后拼接字符串 rgb(255,255,255) 格式
js
function getRandomColor(flag = true) {
  if (flag) {
    let arr = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"];
    let color = "#";
    for (let i = 0; i < 6; i++) {
      let index = Math.floor(Math.random() * 16);
      color += arr[index];
    }
    return color;
  } else {
    let r = Math.floor(Math.random() * 256);
    let g = Math.floor(Math.random() * 256);
    let b = Math.floor(Math.random() * 256);
    return `rgb(${r},${g},${b})`;
  }
}

console.log(getRandomColor(true)); // #f6d5e8
console.log(getRandomColor(false)); // rgb(9,34,60)
console.log(getRandomColor()); // #b5f6d5

拓展

术语解释

术语解释举例
关键字在 JavaScript 中有特殊意义的词汇letvarfunctionifelseswitchcasebreak
保留字在目前的 JavaScript 中没意义,但未来可能会具有特殊意义的词汇intshortlongchar
标识(标识符)变量名、函数名的另一种叫法
表达式能产生值的代码,一般配合运算符出现10 + 3age >= 18
语句一段可执行的代码If ()for()

基本数据类型和引用数据类型

  • 简单类型又叫做基本数据类型或者值类型,复杂类型又叫做引用类型

  • 值类型:简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此叫做值类型

    • stringnumberbooleanundefinednull
  • 引用类型:复杂数据类型,在存储时变量中存储的仅仅是地址(引用),因此叫做引用数据类型

    • 通过 new 关键字创建的对象(系统对象、自定义对象),如 ObjectArrayDate

堆和栈

堆和栈是内存管理的两个重要概念。栈内存是一种特殊的内存区域,用于存储函数的局部变量、参数和返回值等。堆内存则用于动态分配内存,以存储对象和复杂数据结构等。

堆是用来存储引用类型数据(对象、数组、函数等)的区域,其分配内存的方式是通过动态分配内存来实现的。当我们声明一个对象时,JavaScript 引擎会在堆中分配一块内存来存储该对象,并返回该对象的引用地址,这个引用地址就是我们平时所说的对象的变量名。

栈是用来存储基本类型数据(数字、布尔、字符串等)和函数调用时的上下文信息的区域,其分配内存的方式是通过静态分配内存来实现的。当我们声明一个基本类型的变量时,JavaScript 引擎会在栈中分配一块内存来存储该变量的值,当该变量被销毁时,这块内存也会被释放。

堆和栈的区别在于,堆是动态分配内存,可以根据需要动态地增加或减少内存空间,而栈是静态分配内存,分配的内存空间是固定的,无法动态增加或减少。另外,堆中的数据是可以被多个变量引用的,而栈中的数据只能被一个变量引用。

总的来说,堆和栈的区别在于它们分配内存的方式和存储的数据类型不同,JavaScript 引擎会根据需要自动选择使用堆和栈来存储数据。了解堆和栈的区别可以帮助我们更好地理解 JavaScript 内存管理机制,从而编写更高效、更可靠的代码。


具体区别如下:

  1. 分配方式。

    栈是一种静态的分配方式,它的内存大小是固定的,变量的空间在编译期就已经确定,因此效率高,但是存储空间有限制,大小受到操作系统的限制。

    而堆是一种动态的分配方式,大小不固定,内存的分配和释放需要由程序员进行管理,分配效率比栈慢,但是可以使用的空间更大。

  2. 存储内容。

    栈内存用于存储简单类型的变量和对象的引用,当一个函数被调用时,它的局部变量和参数就会被分配到栈上。当函数返回时,这些局部变量和参数所占用的内存就会被释放掉,栈也会相应地收缩。

    堆内存用于存储复杂类型的变量,如对象、数组等。当我们使用 new 关键字创建一个对象时,这个对象就会被分配到堆上。堆上的对象可以被多个引用指向,因此可以被多个函数共享,这也是 JavaScript 中引用类型的特点。

需要注意的是,JavaScript 中的基本类型(Number、Boolean、String、Null、Undefined、Symbol)是存储在栈内存中的,而引用类型(Object、Array、Function 等)的值则是存储在堆内存中的,而变量实际上存储的是对象在堆中的地址,也就是指针。

总的来说,栈内存的空间较小,但是存取速度快,而堆内存的空间较大,但是存取速度较慢,需要由程序员手动管理。

堆栈空间分配区别

  • 栈(操作系统):由操作系统自动分配释放存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈;
    • 简单数据类型存放到栈里面
  • 堆(操作系统):存储复杂类型 (对象),一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。
    • 引用数据类型存放到堆里面

简单类型的内存分配

  • 值类型(简单数据类型): stringnumberbooleanundefinednull
  • 值类型变量的数据直接存放在变量(栈空间)中

复杂类型的内存分配

  • 引用类型(复杂数据类型):通过 new 关键字创建的对象(系统对象、自定义对象),如 ObjectArrayDate
  • 引用类型变量(栈空间)里存放的是地址,真正的对象实例存放在堆空间中

案例 - 学成在线页面渲染案例

学成在线渲染案例(flex 布局 + 空元素) (codepen.io) > 学成在线渲染案例(浮动+margin-right) (codepen.io)

需求:根据数据渲染列表页面

html
<div class="container-types recommended">
  <div class="types-title">
    <h3 class="title">推荐课程</h3>
    <a class="more-btn" href="#">查看更多</a>
  </div>
  <div class="course-list clearfix">
    <!-- <a class="course-item" href="#" target="_blank">
        <div class="course-img" style="background-image: url('#');"></div>
        <p class="course-title">xxxx</p>
        <p class="course-info">
          <span class="difficulty">xxxx</span> · <span class="people">xxxx</span> 人在学习
        </p>
      </a> -->
  </div>
</div>
css
* {
  margin: 0;
  padding: 0;
}

ul,
li {
  list-style: none;
}

a {
  text-decoration: none;
  color: #333;
}

.clearfix::after {
  content: "";
  display: block;
  clear: both;
}

.clearfix {
  zoom: 1;
}

.container-types {
  width: 1200px;
  margin: 30px auto;
  background-color: #f5f5f5;
}

.types-title {
  height: 45px;
  line-height: 45px;
  border-bottom: 1px solid #e5e5e5;
}

.types-title .title {
  float: left;
}

.types-title .more-btn {
  float: right;
  font-size: 14px;
  color: #999;
  margin-right: 36px;
}

.types-title .more-btn:hover {
  color: #4ecdc4;
}

.course-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-self: center;
}

.course-item {
  position: relative;
  display: block;
  width: 233px;
  height: 270px;
  margin-bottom: 15px;
  background-color: #fff;
  transition: all 0.3s;
}

.course-item:hover {
  top: -10px;
  box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1);
}

.course-item.empty {
  height: 0;
  visibility: hidden;
}

.course-img {
  display: block;
  width: 233px;
  height: 155px;
}

.course-title {
  margin: 20px 20px 20px 25px;
  font-size: 14px;
  font-weight: 400;
  line-height: 20px;
  /* height: 40px; */
}

.text-trim {
  /* 限制两行 */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}

.course-info {
  margin: 0 20px 0 25px;
  font-size: 12px;
  color: #999;
}

.difficulty,
.people {
  color: #ff7c2d;
}
js
let data = [
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "Think PHP 5.0 博客系统实战项目演练,Think PHP 5.0 博客系统实战项目演练,Think PHP 5.0 博客系统实战项目演练",
    num: 1125,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "Android 网络动态图片加载实战",
    num: 357,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "Angular2 大前端商城实战项目演练,Angular2 大前端商城实战项目演练",
    num: 22250,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "AndroidAPP 实战项目演练",
    num: 389,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "UGUI 源码深度分析案例",
    num: 124,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "Kami2 首页界面切换效果实战演练,Kami2 首页界面切换效果实战演练",
    num: 432,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "UNITY 从入门到精通实战案例",
    num: 888,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "Cocos 深度学习你不会错过的实战,Cocos 深度学习你不会错过的实战",
    num: 590,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "自动添加的模块",
    num: 1000,
  },
];

let courseList = document.querySelector(".course-list");
courseList.innerHTML = "";

for (let i = 0; i < data.length; i++) {
  let courseItem = document.createElement("a");
  courseItem.className = "course-item";
  courseItem.href = "#";
  courseItem.target = "_blank";

  let courseImg = document.createElement("div");
  courseImg.className = "course-img";
  courseImg.style.backgroundImage = `url(${data[i].src})`;
  courseItem.appendChild(courseImg);

  let courseTitle = document.createElement("p");
  courseTitle.classList.add("course-title", "text-trim");
  courseTitle.innerText = data[i].title;
  courseItem.appendChild(courseTitle);

  let courseInfo = document.createElement("p");
  courseInfo.className = "course-info";

  let difficulty = document.createElement("span");
  difficulty.className = "difficulty";
  difficulty.innerText = "初级";
  courseInfo.appendChild(difficulty);

  let separator = document.createElement("span");
  separator.innerText = " • ";
  courseInfo.appendChild(separator);

  let people = document.createElement("span");
  people.className = "people";
  people.innerText = `${data[i].num}`;
  courseInfo.appendChild(people);

  let peopleText = document.createElement("span");
  peopleText.innerText = " 人在学习";
  courseInfo.appendChild(peopleText);

  courseItem.appendChild(courseInfo);
  courseList.appendChild(courseItem);
}

for (let i = 0; i < 5; i++) {
  const empty = document.createElement("a");
  empty.classList.add("course-item", "empty");
  courseList.appendChild(empty);
}
html
<div class="container-types recommended">
  <div class="types-title">
    <h3 class="title">推荐课程</h3>
    <a class="more-btn" href="#">查看更多</a>
  </div>
  <div class="course-list clearfix">
    <!-- <a class="course-item" href="#" target="_blank">
        <div class="course-img" style="background-image: url('#');"></div>
        <p class="course-title">xxxx</p>
        <p class="course-info">
          <span class="difficulty">xxxx</span> · <span class="people">xxxx</span> 人在学习
        </p>
      </a> -->
  </div>
</div>
css
* {
  margin: 0;
  padding: 0;
}

ul,
li {
  list-style: none;
}

a {
  text-decoration: none;
  color: #333;
}

.clearfix::after {
  content: "";
  display: block;
  clear: both;
}

.clearfix {
  zoom: 1;
}

.container-types {
  width: 1200px;
  margin: 30px auto;
  background-color: #f5f5f5;
}

.types-title {
  height: 45px;
  line-height: 45px;
  border-bottom: 1px solid #e5e5e5;
}

.types-title .title {
  float: left;
}

.types-title .more-btn {
  float: right;
  font-size: 14px;
  color: #999;
  margin-right: 36px;
}

.types-title .more-btn:hover {
  color: #4ecdc4;
}

.course-list {
  margin-right: -8.75px;
  margin-bottom: -15px;
}

.course-item {
  position: relative;
  float: left;
  width: 233px;
  height: 270px;
  margin-right: 8.75px;
  margin-bottom: 15px;
  background-color: #fff;
  transition: all 0.3s;
}

.course-item:hover {
  top: -10px;
  box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1);
}

.course-item.empty {
  height: 0;
  visibility: hidden;
}

.course-img {
  display: block;
  width: 233px;
  height: 155px;
}

.course-title {
  margin: 20px 20px 20px 25px;
  font-size: 14px;
  font-weight: 400;
  line-height: 20px;
  /* height: 40px; */
}

.text-trim {
  /* 限制两行 */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}

.course-info {
  margin: 0 20px 0 25px;
  font-size: 12px;
  color: #999;
}

.difficulty,
.people {
  color: #ff7c2d;
}
js
let data = [
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "Think PHP 5.0 博客系统实战项目演练,Think PHP 5.0 博客系统实战项目演练,Think PHP 5.0 博客系统实战项目演练",
    num: 1125,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "Android 网络动态图片加载实战",
    num: 357,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "Angular2 大前端商城实战项目演练,Angular2 大前端商城实战项目演练",
    num: 22250,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "AndroidAPP 实战项目演练",
    num: 389,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "UGUI 源码深度分析案例",
    num: 124,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "Kami2 首页界面切换效果实战演练,Kami2 首页界面切换效果实战演练",
    num: 432,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "UNITY 从入门到精通实战案例",
    num: 888,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "Cocos 深度学习你不会错过的实战,Cocos 深度学习你不会错过的实战",
    num: 590,
  },
  {
    src: "https://source.unsplash.com/random/233x155",
    title: "自动添加的模块",
    num: 1000,
  },
];

let courseList = document.querySelector(".course-list");
courseList.innerHTML = "";

for (let i = 0; i < data.length; i++) {
  let courseItem = document.createElement("a");
  courseItem.className = "course-item";
  courseItem.href = "#";
  courseItem.target = "_blank";

  let courseImg = document.createElement("div");
  courseImg.className = "course-img";
  courseImg.style.backgroundImage = `url(${data[i].src})`;
  courseItem.appendChild(courseImg);

  let courseTitle = document.createElement("p");
  courseTitle.classList.add("course-title", "text-trim");
  courseTitle.innerText = data[i].title;
  courseItem.appendChild(courseTitle);

  let courseInfo = document.createElement("p");
  courseInfo.className = "course-info";

  let difficulty = document.createElement("span");
  difficulty.className = "difficulty";
  difficulty.innerText = "初级";
  courseInfo.appendChild(difficulty);

  let separator = document.createElement("span");
  separator.innerText = " • ";
  courseInfo.appendChild(separator);

  let people = document.createElement("span");
  people.className = "people";
  people.innerText = `${data[i].num}`;
  courseInfo.appendChild(people);

  let peopleText = document.createElement("span");
  peopleText.innerText = " 人在学习";
  courseInfo.appendChild(peopleText);

  courseItem.appendChild(courseInfo);
  courseList.appendChild(courseItem);
}